package com.earthea.login.common.security;

import com.earthea.cache.CacheService;
import com.earthea.cache.memory.MemoryServiceImpl;
import com.earthea.chatbot.domain.User;
import com.earthea.chatbot.login.common.config.security.UserDetailsServiceImpl;
import com.earthea.chatbot.login.common.config.security.context.AuthenticationContextHolder;
import com.earthea.chatbot.login.common.exception.UserPasswordNotMatchException;
import com.earthea.chatbot.login.common.exception.UserPasswordRetryLimitExceedException;
import com.earthea.chatbot.login.service.UserService;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.when;

@PowerMockIgnore({"javax.security.*", "javax.net.*", "javax.crypto.*", "javax.management.*", "org.xml.*", "org.w3c.*"})
@RunWith(PowerMockRunner.class)
public class UserDetailsServiceImplTest {

    @InjectMocks
    private UserDetailsServiceImpl userDetailsService;

    @Mock
    private UserService userService;

    @Mock
    private Authentication authentication;

    private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    private final CacheService cacheService = new MemoryServiceImpl();

    @Before
    public void before() throws Exception {
        when(authentication.getName()).thenReturn("username");
        when(authentication.getCredentials()).thenReturn("password");
        Whitebox.setInternalState(userDetailsService, "encoder", encoder);
        Whitebox.setInternalState(userDetailsService, "maxRetryCount", 5);
        Whitebox.setInternalState(userDetailsService, "lockTime", 1);
        Whitebox.setInternalState(userDetailsService, "cacheService", cacheService);
    }

    @Test
    public void testLoadUserByUsername_UsernameNotFoundException() {
        when(userService.selectByUser(anyString())).thenReturn(null);
        UsernameNotFoundException exception = Assert.assertThrows(UsernameNotFoundException.class, () -> userDetailsService.loadUserByUsername("username"));
        assertThat(exception.getMessage()).isEqualTo("用户不存在/密码错误");
    }

    @Test
    public void testLoadUserByUsername_UserPasswordNotMatchException() {
        AuthenticationContextHolder.setContext(authentication);
        User user = new User();
        user.setUsername("username");
        user.setPassword(encoder.encode("pwd")); // 密码错误
        when(userService.selectByUser(anyString())).thenReturn(user);
        for (int i = 0; i < 5; i++) {
            UserPasswordNotMatchException exception = Assert.assertThrows(UserPasswordNotMatchException.class, () -> userDetailsService.loadUserByUsername("username"));
            assertThat(exception.getMessage()).isEqualTo("用户不存在/密码错误");
        }
        UserPasswordRetryLimitExceedException exception = Assert.assertThrows(UserPasswordRetryLimitExceedException.class, () -> userDetailsService.loadUserByUsername("username"));
        assertThat(exception.getMessage()).isEqualTo("密码输入错误5次，帐户锁定1分钟");
    }

    @Test
    public void testLoadUserByUsername_success() {
        AuthenticationContextHolder.setContext(authentication);
        User user = new User();
        user.setUsername("username");
        user.setPassword(encoder.encode("password")); // 密码正确
        when(userService.selectByUser(anyString())).thenReturn(user);
        UserDetails userDetails = userDetailsService.loadUserByUsername("username");
        assertThat(userDetails.getUsername()).isEqualTo("username");
        assertThat(userDetails.getPassword()).isNotEmpty();
    }
}